﻿/*
 * KLF Headless Browser
 * Written by Kris Oye <kristianoye@gmail.com>
 * Copyright (C) 2011-2012.  All rights reserved.
 */
namespace KLF.Lib.Browser.DOM.HTML
{
    #region Using Statements

    using System;
    using System.Net;
    using System.Linq;
    using System.Text;
    using System.Collections.Generic;
    using System.Text.RegularExpressions;

    using Jint.Native;

    using KLF.Lib.Browser.DOM.Events;
    using KLF.Lib.Browser.DOM.Parser;
    using KLF.Lib.Browser.DOM.Style;
    
    using KLF.Lib.Browser.JavaScriptHelpers;

    using KLF.Lib.Browser.DOM.CSS;
    using KLF.Lib.Browser.DOM.CSS.SAC;
    using KLF.Lib.Browser.DOM.CSS.SAC.Selectors;

    using KLF.Lib.Browser.DOM.View;
    using Jint;
    using KLF.Lib.Browser.XMLHTTP;

    #endregion

    public class HTMLDocument : Document, IDocumentEvent, IViewCSS, IPropertyBag
    {
        #region Properties

        internal override DocumentState _state
        {
            get
            {
                return base._state;
            }
            set
            {
                if (value == DocumentState.Interactive)
                {
                    Event e = createEvent(HTMLConstants.HTML_EVENT_HTMLEVENTS);
                    e.type = HTMLConstants.HTML_EVENT_DOM_CONTENT_LOADED;
                    dispatchEvent(e);
                }
                if (value == DocumentState.Complete)
                {
                    Event e = createEvent(HTMLConstants.HTML_EVENT_HTMLEVENTS);
                    e.type = HTMLConstants.HTML_EVENT_LOAD;
                    dispatchEvent(e);
                }
                base._state = value;
            }
        }

        internal HTMLLint Lint { get; set; }

        internal Node WriteContext { get; set; }

        #endregion

        #region DOM Level 1 Compliance

        public override DOMImplementation implementation
        {
            get
            {
                return new HTMLImplementation();
            }
        }

        #endregion

        #region DOM Level 2 Events Compliance

        /// <summary>
        /// Stub / helper function for logging ActiveX requests.
        /// </summary>
        /// <param name="comSpec"></param>
        /// <returns></returns>
        public ActiveXObject createActiveXObject(string comSpec)
        {
            return new ActiveXObject(comSpec);
        }

        /// <summary>
        /// The eventType parameter specifies the type of Event interface 
        /// to be created. If the Event interface specified is supported by the
        /// implementation this method will return a new Event of the interface
        /// type requested. If the Event is to be dispatched via the 
        /// dispatchEvent method the appropriate event init method must be 
        /// called after creation in order to initialize the Event's values. As
        /// an example, a user wishing to synthesize some kind of UIEvent would
        /// call createEvent with the parameter "UIEvents". The initUIEvent 
        /// method could then be called on the newly created UIEvent to set the
        /// specific type of UIEvent to be dispatched and set its context
        /// information.
        /// 
        /// The createEvent method is used in creating Events when it is either 
        /// inconvenient or unnecessary for the user to create an Event 
        /// themselves. In cases where the implementation provided Event is 
        /// insufficient, users may supply their own Event implementations for
        /// use with the dispatchEvent method.
        /// </summary>
        /// <param name="eventType"></param>
        /// <returns></returns>
        public Event createEvent(string eventType)
        {
            switch (eventType.ToLowerInvariant())
            {
                case HTMLConstants.HTML_EVENT_COMPOSITIONEVENT:
                    return new CompositionEvent(eventType);

                case HTMLConstants.HTML_EVENT_FOCUSEVENT:
                    return new FocusEvent(eventType);

                case HTMLConstants.HTML_EVENT_KEYBOARDEVENT:
                    return new KeyboardEvent(eventType);

                case HTMLConstants.HTML_EVENT_MOUSEEVENT:
                    return new MouseEvent(eventType);

                case HTMLConstants.HTML_EVENT_MUTATIONEVENT:
                    return new MutationEvent(eventType);

                case HTMLConstants.HTML_EVENT_PROGRESSEVENT:
                    return new ProgressEvent(eventType);

                default:
                case HTMLConstants.HTML_EVENT_HTMLEVENTS:
                    return new Event(eventType);
            }
        }

        /// <summary>
        /// All modern browsers (IE7+, Firefox, Chrome, Safari, and Opera) have a built-in XMLHttpRequest object.
        /// </summary>
        /// <returns></returns>
        public XMLHttpRequest createXmlHttpRequest()
        {
            return new XMLHttpRequest();
        }

        #endregion

        #region DOM Level 2 HTML Compliance

        /// <summary>
        /// Returns the currently focused element, that is, the element that 
        /// will get keystroke events if the user types any. This attribute 
        /// is read only.
        /// 
        /// Often this will return an <input> or <textarea> object, if it has
        /// the text selection at the time.  If so, you can get more detail by
        /// using the element's selectionStart and selectionEnd properties.  
        /// Other times the focused element might be a <select> element (menu)
        /// or an <input> element, of type button, checkbox or radio.
        /// 
        /// When there is no selection, the active element is the page's <body>. 
        /// </summary>
        public HTMLElement activeElement
        {
            get
            {
                return _activeElement ?? this.body;
            }
            internal set
            {
                _activeElement = value;
            }
        }
        internal HTMLElement _activeElement;

        /// <summary>
        /// The addBinding method attaches the specified binding (and any 
        /// bindings that the binding inherits from) to an element. This call
        /// is not necessarily synchronous. The binding may not be attached
        /// yet when the call completes. See here for more information.
        /// </summary>
        /// <param name="elt"></param>
        /// <param name="bindingUrl"></param>
        public void addBinding(Element elt, string bindingUrl)
        {
            throw new NotImplementedException();
        }


        /// <summary>
        /// 
        /// </summary>
        [Obsolete]
        public string alinkColor
        {
            get
            {
                return body.getAttribute(HTMLConstants.HTML_ATT_ALINK);
            }
            set
            {
                if (String.IsNullOrEmpty(value))
                    body.removeAttribute(HTMLConstants.HTML_ATT_ALINK);
                else
                    body.setAttribute(HTMLConstants.HTML_ATT_ALINK, value);
            }
        }

        /// <summary>
        /// A collection of all the anchor (A) elements in a document with a 
        /// value for the name attribute.
        /// 
        /// Note: For reasons of backward compatibility, the returned set of
        /// anchors only contains those anchors created with the name 
        /// attribute, not those created with the id attribute. Note that in 
        /// [XHTML 1.0], the name attribute (see section 4.10) has no semantics 
        /// and is only present for legacy user agents: the id attribute is 
        /// used instead. Users should prefer the iterator mechanisms provided 
        /// by [DOM Level 2 Traversal] instead.
        /// </summary>
        public HTMLCollection anchors
        {
            get
            {
                return new HTMLCollection(this, Anchors.Cast<Node>());
            }
        }

        public IEnumerable<HTMLAnchorElement> Anchors
        {
            get
            {
                return Elements
                    .Where(e => e is HTMLAnchorElement)
                    .Cast<HTMLAnchorElement>();
            }
        }

        /// <summary>
        /// A collection of all the OBJECT elements that include applets and 
        /// APPLET (deprecated) elements in a document.
        /// </summary>
        [Obsolete("The applets property was deprecated in DOM Level 2 HTML", false)]
        public HTMLCollection applets
        {
            get
            {
                return new HTMLCollection(this, Elements
                    .Where(e => e is HTMLAppletElement)
                    .Cast<Node>());
            }
        }

        /// <summary>
        /// Background color (deprecated)
        /// </summary>
        [Obsolete]
        public string bgColor
        {
            get
            {
                return body.getAttribute(HTMLConstants.HTML_ATT_BGCOLOR);
            }
            set
            {
                if (String.IsNullOrEmpty(value))
                    body.removeAttribute(HTMLConstants.HTML_ATT_BGCOLOR);
                else
                    body.setAttribute(HTMLConstants.HTML_ATT_BGCOLOR, value);
            }
        }

        /// <summary>
        /// The element that contains the content for the document. In 
        /// documents with BODY contents, returns the BODY element. In 
        /// frameset documents, this returns the outermost FRAMESET 
        /// element.
        /// </summary>
        public HTMLElement body
        {
            get
            {
                return (HTMLElement)Elements
                    .DefaultIfEmpty(null)
                    .FirstOrDefault(e => 
                        e is HTMLBodyElement || e is HTMLFrameSetElement);
            }
        }

        /// <summary>
        /// Closes a document stream opened by open() and forces rendering. 
        /// </summary>
        public virtual void close()
        {
        }

        public string cookie
        {
            get
            {
                if (Context != null)
                {
                    return Context.Cookies.ToString();
                }
                else
                {
                    return "";
                }
            }
            set
            {
                if (Context != null)
                {
                    Regex r = new Regex(@"^[;]?((?<key>([^;=]+)=(?<val>[^;]+))", RegexOptions.ExplicitCapture);

                    if (r.IsMatch(value))
                    {
                        MatchCollection mc = r.Matches(value);

                        foreach(Match m in mc)
                        {
                        }
                    }
                }
            }
        }

        /// <summary>
        /// Ensures all elements in an HTML Document are HTML Elements
        /// </summary>
        /// <param name="tagName"></param>
        /// <returns></returns>
        public override Element createElement(string tagName)
        {
            String tag = tagName.ToLowerInvariant();

            switch (tag)
            {
                case HTMLConstants.HTML_TAG_ANCHOR:
                    return new HTMLAnchorElement(this);

                case HTMLConstants.HTML_TAG_APPLET:
                    return new HTMLAppletElement(this);

                case HTMLConstants.HTML_TAG_AREA:
                    return new HTMLAreaElement(this);

                case HTMLConstants.HTML_TAG_BASE:
                    return new HTMLBaseElement(this);

                case HTMLConstants.HTML_TAG_BASEFONT:
                    return new HTMLBaseFontElement(this);

                case HTMLConstants.HTML_TAG_BLOCKQUOTE:
                    return new HTMLQuoteElement(this, tag);

                case HTMLConstants.HTML_TAG_BODY:
                    return new HTMLBodyElement(this);

                case HTMLConstants.HTML_TAG_BR:
                    return new HTMLBRElement(this);

                case HTMLConstants.HTML_TAG_BUTTON:
                    return new HTMLButtonElement(this);

                case HTMLConstants.HTML_TAG_DIR:
                    return new HTMLDirectoryElement(this);

                case HTMLConstants.HTML_TAG_DIV:
                    return new HTMLDivElement(this);

                case HTMLConstants.HTML_TAG_DL:
                    return new HTMLDListElement(this);

                case HTMLConstants.HTML_TAG_FIELDSET:
                    return new HTMLFieldSetElement(this);

                case HTMLConstants.HTML_TAG_FONT:
                    return new HTMLFontElement(this);

                case HTMLConstants.HTML_TAG_FORM:
                    return new HTMLFormElement(this);

                case HTMLConstants.HTML_TAG_FRAME:
                    return new HTMLFrameElement(this);

                case HTMLConstants.HTML_TAG_FRAMESET:
                    return new HTMLFrameSetElement(this);

                case HTMLConstants.HTML_TAG_H1:
                case HTMLConstants.HTML_TAG_H2:
                case HTMLConstants.HTML_TAG_H3:
                case HTMLConstants.HTML_TAG_H4:
                case HTMLConstants.HTML_TAG_H5:
                case HTMLConstants.HTML_TAG_H6:
                    return new HTMLHeadingElement(this, tag);

                case HTMLConstants.HTML_TAG_HTML:
                    return new HTMLHtmlElement(this) { version = "4.01" };

                case HTMLConstants.HTML_TAG_HEAD:
                    return new HTMLHeadElement(this);

                case HTMLConstants.HTML_TAG_HR:
                    return new HTMLHRElement(this);

                case HTMLConstants.HTML_TAG_IFRAME:
                    return new HTMLIFrameElement(this);

                case HTMLConstants.HTML_TAG_IMG:
                    return new HTMLImageElement(this);

                case HTMLConstants.HTML_TAG_INPUT:
                    return new HTMLInputElement(this);

                case HTMLConstants.HTML_TAG_ISINDEX:
                    #pragma warning disable 612, 618
                    return new HTMLIsIndexElement(this);
                    #pragma warning restore 612, 618

                case HTMLConstants.HTML_TAG_LABEL:
                    return new HTMLLabelElement(this);

                case HTMLConstants.HTML_TAG_LEGEND:
                    return new HTMLLabelElement(this);

                case HTMLConstants.HTML_TAG_LISTITEM:
                    return new HTMLLIElement(this);

                case HTMLConstants.HTML_TAG_LINK:
                    return new HTMLLinkElement(this);

                case HTMLConstants.HTML_TAG_MAP:
                    return new HTMLMapElement(this);

                case HTMLConstants.HTML_TAG_MENU:
                    return new HTMLMenuElement(this);

                case HTMLConstants.HTML_TAG_META:
                    return new HTMLMetaElement(this);

                case HTMLConstants.HTML_TAG_MOD:
                    return new HTMLModElement(this);

                case HTMLConstants.HTML_TAG_OBJECT:
                    return new HTMLObjectElement(this);

                case HTMLConstants.HTML_TAG_LIST_ORDERED:
                    return new HTMLOListElement(this);

                case HTMLConstants.HTML_TAG_P:
                    return new HTMLParagraphElement(this);

                case HTMLConstants.HTML_TAG_PARAM:
                    return new HTMLParamElement(this);

                case HTMLConstants.HTML_TAG_PRE:
                    return new HTMLPreElement(this);

                case HTMLConstants.HTML_TAG_Q:
                    return new HTMLQuoteElement(this, tag);

                case HTMLConstants.HTML_TAG_OPTGROUP:
                    return new HTMLOptGroupElement(this);

                case HTMLConstants.HTML_TAG_OPTION:
                    return new HTMLOptionElement(this);

                case HTMLConstants.HTML_TAG_SCRIPT:
                    return new HTMLScriptElement(this);

                case HTMLConstants.HTML_TAG_SELECT:
                    return new HTMLSelectElement(this);

                case HTMLConstants.HTML_TAG_STYLE:
                    return new HTMLStyleElement(this);

                case HTMLConstants.HTML_TAG_TABLE:
                    return new HTMLTableElement(this);

                case HTMLConstants.HTML_TAG_TABLECAPTION:
                    return new HTMLTableCaptionElement(this);

                case HTMLConstants.HTML_TAG_TABLECELL:
                    return new HTMLTableCellElement(this);

                case HTMLConstants.HTML_TAG_TABLECOL:
                    return new HTMLTableColElement(this);

                case HTMLConstants.HTML_TAG_TABLEBODY:
                case HTMLConstants.HTML_TAG_TABLEFOOT:
                case HTMLConstants.HTML_TAG_TABLEHEAD:
                    return new HTMLTableSectionElement(this, tag);

                case HTMLConstants.HTML_TAG_TABLEROW:
                    return new HTMLTableRowElement(this);

                case HTMLConstants.HTML_TAG_TEXTAREA:
                    return new HTMLTextAreaElement(this);

                case HTMLConstants.HTML_TAG_TITLE:
                    return new HTMLTitleElement(this);

                case HTMLConstants.HTML_TAG_LIST_UNORDERED:
                    return new HTMLUListElement(this);

                default:
                    return new HTMLElement(this, tag);
            }
        }
        
        /// <summary>
        /// Returns the <script> element whose script is currently being processed.
        /// </summary>
        public HTMLScriptElement currentScript
        {
            get;
            internal set;
        }

        /// <summary>
        /// 
        /// </summary>
        public override Element documentElement
        {
            get
            {
                return Elements
                    .DefaultIfEmpty(null)
                    .FirstOrDefault(e => (e as HTMLHtmlElement) != null);
            }
            internal set
            {
                throw new NotImplementedException();
            }
        }

        /// <summary>
        /// Returns the parsed representation of the document source.
        /// </summary>
        public string DocumentSource
        {
            get
            {
                StringBuilder sb = new StringBuilder();
                int depth = 0;

                foreach (Node n in Children)
                {
                    if (n is Element)
                    {
                        sb.Append((n as Element).GetIndentedHtml(depth));
                    }
                    else
                    {
                        if (n.textContent.Trim().Length > 0)
                            sb.Append(n.textContent + "\n");
                    }
                }
                return sb.ToString();
            }
        }

        /// <summary>
        /// Text direction: ltr = left-to-right / rtl = right-to-left
        /// </summary>
        public string dir
        {
            get
            {
                return body.getAttribute(HTMLConstants.HTML_ATT_DIR);
            }
            set
            {
                body.setAttribute(HTMLConstants.HTML_ATT_DIR, value);
            }
        }

        /// <summary>
        /// The domain name of the server that served the document, or null
        /// if the server cannot be identified by a domain name.
        /// </summary>
        public string domain
        {
            get
            {
                return _domain != null ? _domain.Value : "";
            }
            internal set
            {
                _domain = new StringLookup(value);
            }
        }
        internal StringLookup _domain;

        /// <summary>
        /// Evaluates an XPath expression.
        /// </summary>
        /// <param name="xpathExpression"></param>
        /// <param name="contextNode"></param>
        /// <param name="namespaceResolver"></param>
        /// <param name="resultType"></param>
        /// <param name="result"></param>
        public void evaluate(
            string xpathExpression,
            Node contextNode,
            object namespaceResolver,
            int resultType,
            object result)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Foreground color
        /// </summary>
        public string fgColor
        {
            get
            {
                return body.getAttribute(HTMLConstants.HTML_ATT_FGCOLOR);
            }
            set
            {
                if (String.IsNullOrEmpty(value))
                    body.removeAttribute(HTMLConstants.HTML_ATT_FGCOLOR);
                else
                    body.setAttribute(HTMLConstants.HTML_ATT_FGCOLOR, value);
            }
        }

        /// <summary>
        /// A collection of all the IMG elements in a document. The behavior is
        /// limited to IMG elements for backwards compatibility.
        /// 
        /// Note: As suggested by [HTML 4.01], to include images, authors may 
        /// use the OBJECT element or the IMG element. Therefore, it is recommended 
        /// not to use this attribute to find the images in the document but 
        /// getElementsByTagName with HTML 4.01 or getElementsByTagNameNS 
        /// with XHTML 1.0.
        /// </summary>
        public HTMLCollection forms
        {
            get
            {
                IEnumerable<HTMLFormElement> _F = Forms;
                if (_forms == null || _forms.length != _F.Count())
                {
                    if (Context.Script != null)
                    {
                        _forms = new HTMLCollection(this, _F.Cast<Node>());
                        foreach (HTMLFormElement f in _F)
                        {
                            var result = Context.Script.Run(
                                String.Format(
                                    "document.forms.{0} = document.forms['{0}']",
                                    f.name));
                        }
                    }
                    else
                    {
                        return new HTMLCollection(this, _F.Cast<Node>());
                    }
                }
                return _forms;
            }
        }
        HTMLCollection _forms;

        /// <summary>
        /// Returns a collection of form objects contained within the document.
        /// </summary>
        internal IEnumerable<HTMLFormElement> Forms
        {
            get
            {
                return Elements
                    .Where(e => e is HTMLFormElement)
                    .Cast<HTMLFormElement>();
            }
        }

        /// <summary>
        /// Returns a wrapped collection of frames.  The collection should 
        /// always refer back to the DOM when queried in case there are changes
        /// (i.e. a frame added or removed).  The collection
        /// </summary>
        public HTMLWrappedCollection<HTMLFrameElement> frames
        {
            get
            {
                if (_frames == null)
                    _frames = new HTMLWrappedCollection<HTMLFrameElement>(this, "Frames");
                return _frames;
            }
        }
        internal HTMLWrappedCollection<HTMLFrameElement> _frames;

        /// <summary>
        /// Returns a collection of frame objects contained within the document.
        /// </summary>
        internal IEnumerable<HTMLFrameElement> Frames
        {
            get
            {
                return Elements
                    .Where(e => e is HTMLFrameElement)
                    .Cast<HTMLFrameElement>();
            }
        }

        /// <summary>
        /// 
        /// </summary>
        /// <param name="className"></param>
        public NodeList getElementsByClassName(string className)
        {
            string[] classes = className.Split(' ')
                .Where(s => s.Trim().Length > 0)
                .ToArray();

            return new NodeList(Elements
                .Where(e =>
                {
                    string[] myClasses = e.getAttribute(HTMLConstants.HTML_ATT_CLASS)
                        .Split(' ')
                        .Where(s => s.Trim().Length > 0)
                        .ToArray();

                    return classes
                        .Distinct()
                        .Union(myClasses.Distinct()).Count() == classes.Count();
                })
                .Cast<Node>());
        }

        /// <summary>
        /// With [HTML 4.01] documents, this method returns the (possibly empty)
        /// collection of elements whose name value is given by elementName. In
        /// [XHTML 1.0] documents, this methods only return the (possibly empty)
        /// collection of form controls with matching name. This method is 
        /// case sensitive. 
        /// </summary>
        /// <param name="elementName">The name attribute value for an element.</param>
        /// <returns>The matching elements.</returns>
        public NodeList getElementsByName(string elementName)
        {
            return new NodeList(Elements
                .Where(e => e.getAttribute(HTMLConstants.HTML_ATT_NAME).Equals(elementName))
                .Cast<Node>());
        }

        /// <summary>
        /// Returns the first element matching the selector.
        /// </summary>
        /// <param name="selectorString"></param>
        /// <returns></returns>
        public HTMLElement querySelector(string selectorString)
        {
            SACParser p = new SACParser();
            var sel = p.ParseSelector(selectorString);
            var result = Elements
                .DefaultIfEmpty(null)
                .Cast<HTMLElement>()
                .FirstOrDefault(e => sel.Matches(e as HTMLElement));
            return result;
        }

        /// <summary>
        /// Returns all of the elements matching the selector.
        /// </summary>
        /// <param name="selectorString"></param>
        /// <returns></returns>
        public NodeList querySelectorAll(string selectorString)
        {
            SACParser p = new SACParser();
            var sel = p.ParseSelector(selectorString);

            return new NodeList(Elements
                .Cast<Node>()
                .Where(e => sel.Matches(e as HTMLElement)));
        }

        /// <summary>
        /// Returns the head of the document.
        /// </summary>
        public HTMLHeadElement head
        {
            get
            {
                return Elements
                    .DefaultIfEmpty(null)
                    .Cast<HTMLHeadElement>()
                    .First(e => e is HTMLHeadElement);
            }
        }

        /// <summary>
        /// A collection of all the IMG elements in a document. The behavior is
        /// limited to IMG elements for backwards compatibility.
        /// 
        /// Note: As suggested by [HTML 4.01], to include images, authors may 
        /// use the OBJECT element or the IMG element. Therefore, it is recommended 
        /// not to use this attribute to find the images in the document but 
        /// getElementsByTagName with HTML 4.01 or getElementsByTagNameNS 
        /// with XHTML 1.0.
        /// </summary>
        public HTMLCollection images
        {
            get
            {
                return new HTMLCollection(this, Elements
                    .Where(e => e is HTMLImageElement)
                    .Cast<Node>());
            }
        }

        /// <summary>
        /// A collection of all AREA elements and anchor (A) elements in a 
        /// document with a value for the href attribute.
        /// </summary>
        public HTMLCollection links
        {
            get
            {
                return new HTMLCollection(this, Elements
                    .Where(e => e is HTMLAreaElement || e is HTMLAnchorElement)
                    .Cast<Node>());
            }
        }

        /// <summary>
        /// Open a document stream for writing. If a document exists in the 
        /// target, this method clears it. 
        /// </summary>
        public void open()
        {
            removeChild(documentElement);
            appendChild(createElement(HTMLConstants.HTML_TAG_HTML));
        }

        /// <summary>
        /// Returns the URI [IETF RFC 2396] of the page that linked to this
        /// page. The value is an empty string if the user navigated to the 
        /// page directly (not through a link, but, for example, via 
        /// a bookmark).
        /// </summary>
        public string referrer
        {
            get
            {
                return _referrer != null ? _referrer.Value : "";
            }
            internal set
            {
                _referrer = new StringLookup(value);
            }
        }
        internal StringLookup _referrer;

        /// <summary>
        /// NOT IMPLEMENTED The removeBinding method detaches the specified 
        /// binding (and any bindings that the binding inherits from 
        /// explicitly using the extends attribute) from the element. See
        /// here for more information.
        /// </summary>
        /// <param name="elt"></param>
        /// <param name="bindingURL"></param>
        public void removeBinding(Element elt, string bindingURL)
        {
            throw new NotImplementedException();
        }

        /// <summary>
        /// Returns all of the scripts in the collection.
        /// </summary>
        public HTMLCollection scripts
        {
            get
            {
                return new HTMLCollection(this,
                    Elements.Where(e => e is HTMLScriptElement)
                    .Cast<Node>());
            }
        }

        /// <summary>
        /// 
        /// </summary>
        public StyleSheetList styleSheets
        {
            get
            {
                throw new NotImplementedException();
            }
        }

        public object this[string index]
        {
            get
            {
                return null;
            }
            set
            {
                string s = value.ToString();
            }
        }

        /// <summary>
        /// The title of a document as specified by the TITLE element 
        /// in the head of the document.
        /// </summary>
        public string title
        {
            get
            {
                var result = Elements
                    .DefaultIfEmpty(createElement(HTMLConstants.HTML_TAG_TITLE))
                    .FirstOrDefault(e => e is HTMLTitleElement);

                return result.textContent;
            }
            set
            {
                var result = Elements
                    .DefaultIfEmpty(null)
                    .FirstOrDefault(e => e is HTMLTitleElement);

                if (result == null)
                {
                    result = Elements
                        .DefaultIfEmpty(null)
                        .FirstOrDefault(e => e is HTMLHeadElement);

                    if (result != null)
                    {
                        Element title = createElement(HTMLConstants.HTML_TAG_TITLE);
                        title.textContent = value;
                        result.appendChild(title);
                    }
                }
                else
                {
                    result.textContent = value;
                }
            }
        }

        public string vlinkColor
        {
            get
            {
                return body.getAttribute(HTMLConstants.HTML_ATT_VLINK);
            }
            set
            {
                if (String.IsNullOrEmpty(value))
                    body.removeAttribute(HTMLConstants.HTML_ATT_VLINK);
                else
                    body.setAttribute(HTMLConstants.HTML_ATT_VLINK, value);
            }
        }

        /// <summary>
        /// Write a string of text to a document stream opened by open(). Note
        /// that the function will produce a document which is not necessarily 
        /// driven by a DTD and therefore might be produce an invalid result 
        /// in the context of the document. 
        /// </summary>
        /// 
        /// <param name="str">The string to be parsed into some structure in 
        /// the document structure model.</param>
        public void write(string str)
        {
            if (WriteContext == null)
            {
                open();
                documentElement.appendChild(createElement(HTMLConstants.HTML_TAG_HEAD));
                WriteContext = documentElement.appendChild(createElement(HTMLConstants.HTML_TAG_BODY));
            }
            if (WriteContext != null && WriteContext.Parent != null)
            {
                DocumentBuilder.ParseFragment(str, this, WriteContext.Parent);
            }
        }

        /// <summary>
        /// Write a string of text followed by a newline character to a 
        /// document stream opened by open(). Note that the function will 
        /// produce a document which is not necessarily driven by a DTD and
        /// therefore might be produce an invalid result in the context of 
        /// the document 
        /// </summary>
        /// 
        /// <param name="str">The string to be parsed into some structure 
        /// in the document structure model.</param>
        public void writeln(string str)
        {
            write(str + "\n");
        }

        /// <summary>
        /// The absolute URI [IETF RFC 2396] of the document.
        /// </summary>
        public string URL
        {
            get
            {
                return _URL != null ? _URL.Value : "";
            }
            internal set
            {
                _URL = new StringLookup(value);
            }
        }
        internal StringLookup _URL;

        #endregion

        #region Internal-Use Methods

        internal void AddWarning(string fmt, params string[] args)
        {
            String str = (args.Length > 0 ? String.Format(fmt, args) : fmt);
            if (Lint != null)
                Lint.AddWarning(str);
        }

        internal void AddStrict(string fmt, params string[] args)
        {
            String str = (args.Length > 0 ? String.Format(fmt, args) : fmt);
            if (Lint != null)
                Lint.AddStrict(str);
        }

        internal void AddError(string fmt, params string[] args)
        {
            String str = (args.Length > 0 ? String.Format(fmt, args) : fmt);
            if (Lint != null)
                Lint.AddError(str);
        }

        internal List<HTMLElement> FindAll(Selector s)
        {
            return new List<HTMLElement>(Elements
                .Cast<HTMLElement>()
                .Where(e => s.Matches(e)));
        }

        internal HTMLElement FindFirst(Selector s)
        {
            return Elements
                .DefaultIfEmpty(null)
                .Cast<HTMLElement>()
                .FirstOrDefault(e => s.Matches(e));
        }

        #endregion

        #region DOM Level 2 Events Compliance

        /// <summary>
        /// Runs if the document loading is aborted.
        /// </summary>
        public object onabort
        {
            get;
            set;
        }

        /// <summary>
        /// Runs after a script executes.
        /// </summary>
        public object onafterscriptexecute
        {
            get;
            set;
        }

        /// <summary>
        /// Runs before a script executes.
        /// </summary>
        public object onbeforescriptexecute
        {
            get;
            set;
        }

        /// <summary>
        /// Fires when the element is blurred.
        /// </summary>
        public object onblur
        {
            get;
            set;
        }

        /// <summary>
        /// Fires when the element changes.
        /// </summary>
        public object onchange
        {
            get;
            set;
        }

        /// <summary>
        /// Fires when the element is clicked on.
        /// </summary>
        public object onclick
        {
            get;
            set;
        }

        /// <summary>
        /// Fires when the element is double-clicked.
        /// </summary>
        public object ondblclick
        {
            get;
            set;
        }

        /// <summary>
        /// Fires when the element gets focus.
        /// </summary>
        public object onfocus
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        public object onkeydown
        {
            get;
            set;
        }
        
        /// <summary>
        /// 
        /// </summary>
        public object onkeypress
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        public object onkeyup
        {
            get;
            set;
        }

        /// <summary>
        /// 
        /// </summary>
        public object onload
        {
            get
            {
                var n = body;

                if (n is HTMLBodyElement)
                    return (n as HTMLBodyElement).onload;
                else if (n is HTMLFrameSetElement)
                    return (n as HTMLFrameSetElement).onload;
                else
                    return null;
            }
            set
            {
                var n = body;

                if (n is HTMLBodyElement)
                    (n as HTMLBodyElement).onload = value;
                else if (n is HTMLFrameSetElement)
                    (n as HTMLFrameSetElement).onload = value;
            }
        }

        #endregion

        /// <summary>
        /// Register objects with the script
        /// </summary>
        /// <param name="Script"></param>
        internal void Register(Jint.JintEngine Script)
        {
        }

        #region IViewCSS Members

        public CSSStyleDeclaration getComputedStyle(Element elt, string pseudoElt)
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IAbstractView Members

        public IDocumentView document
        {
            get { return Context.Window; }
        }

        #endregion

        #region IPropertyGetter Members

        public bool TryGetProperty(string index, out JsInstance result)
        {
            result = null;
            return false;
        }

        #endregion

        #region IPropertyBag Members

        public Descriptor Put(string name, Descriptor descriptor)
        {
            throw new NotImplementedException();
        }

        public void Delete(string name)
        {
            throw new NotImplementedException();
        }

        public Descriptor Get(string name)
        {
            throw new NotImplementedException();
        }

        public bool TryGet(string name, out Descriptor descriptor)
        {
            throw new NotImplementedException();
        }

        public int Count
        {
            get { throw new NotImplementedException(); }
        }

        public IEnumerable<Descriptor> Values
        {
            get { throw new NotImplementedException(); }
        }

        #endregion

        #region IEnumerable<KeyValuePair<string,Descriptor>> Members

        public IEnumerator<KeyValuePair<string, Descriptor>> GetEnumerator()
        {
            throw new NotImplementedException();
        }

        #endregion

        #region IEnumerable Members

        System.Collections.IEnumerator System.Collections.IEnumerable.GetEnumerator()
        {
            throw new NotImplementedException();
        }

        #endregion

        public override string ToString()
        {
            return "[object HTMLDocument]";
        }
    }
}
